package DatagramPlusPlus;


import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.net.SocketException;


 /*
 * @author Rafael
 */
public class DatagramPlusPlusPacket {

    private DatagramPacket p;
    
    private DatagramType tipo;
    private byte flags = 0;
    private long numSerie = 0;
    private static long proximoNumSerie = 0;


    // <editor-fold defaultstate="collapsed" desc="Construtores">
    protected DatagramPlusPlusPacket(DatagramPlusPlusPacket packet) {
        tipo = packet.getTipo();
        if(packet.getFlagAck())
            flags = 1;
        else flags = 0;
        numSerie = packet.getNumSerie();
        p = packet.getUDPPacket();
    }

    public DatagramPlusPlusPacket(DatagramType tipo) {
        this.tipo = tipo;
        byte[] buf = {tipo.getTipo()};
        p = new DatagramPacket(buf, buf.length);
    }

    DatagramPlusPlusPacket(byte[] buf, int length) {
        tipo = new DatagramType(DatagramType.DATA);
        setData(buf, 0, length);
    }

    DatagramPlusPlusPacket(byte[] buf, int length, InetAddress address, int port) {
        tipo = new DatagramType(DatagramType.DATA);
        setData(buf, 0, length);
        this.setPort(port);
        this.setAddress(address);
    }

    DatagramPlusPlusPacket(byte[] buf, int offset, int length) {
        tipo = new DatagramType(DatagramType.DATA);
        setData(buf, offset, length);
    }

    DatagramPlusPlusPacket(byte[] buf, int offset, int length, InetAddress address, int port) {
        tipo = new DatagramType(DatagramType.DATA);
        setData(buf, offset, length);
        setAddress(address);
        setPort(port);
    }

    DatagramPlusPlusPacket(byte[] buf, int offset, int length, SocketAddress address) throws SocketException {
        tipo = new DatagramType(DatagramType.DATA);
        setData(buf, offset, length);
        setSocketAddress(address);
    }

    DatagramPlusPlusPacket(byte[] buf, int length, SocketAddress address) throws SocketException {
        tipo = new DatagramType(DatagramType.DATA);
        setData(buf, 0, length);
        setSocketAddress(address);
    }// </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="API DatagramPacket">
    /**
     * @return the IP address of the machine to which this datagram is being sent or from which the datagram was received.
     */
    public InetAddress getAddress() {
        return p.getAddress();
    }

    /**
     * Faz load da informação contida no pacote para as variáveis de instancia
     * @return Os dados contidos no pacote
     */
    public byte[] getData() {

        byte[] buf;
        byte[] dados = null;

        buf = p.getData();
        for(int i = 10 ; i<buf.length ; i++)
            dados[i-10]=buf[i];

        return dados;
    }

    /**
     * @return the length of the data to be sent or the length of the data received.
     */
    public int getLength() {
        return (p.getLength());
    }

    /**
     * @return the offset of the data to be sent or the offset of the data received.
     */
    public int getOffset() {
        return p.getOffset();
    }

    /**
     *
     * @return the port number on the remote host to which this datagram is being sent or from which the datagram was received.
     */
    public int getPort() {
        return p.getPort();
    }

    /**
     * Gets the SocketAddress (usually IP address + port number) of the remote host that this packet is being sent to or is coming from.
     * @return
     */
    public SocketAddress getSocketAddress() {
        return p.getSocketAddress();
    }

    /**
     * Sets the IP address of the machine to which this datagram is being sent.
     * @param iaddr
     */
    public final void setAddress(InetAddress iaddr) {
        p.setAddress(iaddr);
    }

    /**
     * Set the data buffer for this packet.
     * @param buf
     */
    public void setData(byte[] buf) {
        setData(buf, 0, buf.length);
    }

    /**
     * Set the data buffer for this packet.
     * @param dados Array de dados para enviar
     * @param offset marca a posição inicial do array
     * @param length marca a posição final do array
     */
    public final void setData(byte[] dados, int offset, int length) {

        byte[] buf = null;

        tipo.setTipo(DatagramType.DATA);
        byte flags = 0;
        byte[] serial = new byte[8];
        if(numSerie == 0)   {
            serial = getNumSerieByte(proximoNumSerie);
            proximoNumSerie++;
        }
        else
            serial = getNumSerieByte(numSerie);

        buf[0] = tipo.getTipo();
        buf[1] = flags;
        for (int i = 0; i < 8; i++) {
            buf[i + 2] = serial[i];
        }
        // comprimento do overhead e sempre 8+2
        for( int i = 0; i < dados.length ; i++)
            buf[i + 10] = dados[i];
    }
    /**
     * Set the length for this packet.
     * @param length
     */
    public void setLength(int length) {
        p.setLength(length);
    }

    /**
     * Sets the port number on the remote host to which this datagram is being sent.
     * @param iport
     */
    public final void setPort(int iport) {
        p.setPort(iport);
    }

    /**
     * Sets the SocketAddress (usually IP address + port number) of the remote host to which this datagram is being sent.
     * @param address
     */
    public final void setSocketAddress(SocketAddress address) {
        p.setSocketAddress(address);
    }
    // </editor-fold>

    /**
     * Faz update da informação do pacote mediante a informação contida no buffer.
     * Esta funcao deve ser chamada sempre que recebe-se um pacote.
     */
    protected void refreshVars() {
        byte[] buf;

        buf = p.getData();
        tipo.setTipo(buf[0]);
        flags = buf[1];
        numSerie = getNumSerieLong(buf, 2);
    }
    protected void refreshData()    {
        byte[] buf = new byte[1034];
        byte[] serial = new byte[8];

        serial = getNumSerieByte(numSerie);

        buf[0] = tipo.getTipo();
        buf[1] = flags;
        for (int i = 0; i < 8; i++) {
            buf[i + 2] = serial[i];
        }
    }

    public DatagramPacket getUDPPacket()    {
        refreshData();
        return p;
    }

    /**
     * Devolve o tipo do pacote
     * @return o tipo do pacote
     */
    public DatagramType getTipo() {
        if(tipo!=null)
            return tipo;
        System.out.println("Tipo de pacote nulo!");
        return new DatagramType(DatagramType.INIT);
    }

    /**
     * Devolve o numero de serie do pacote
     * @return o numero de serie do pacote
     */
    public long getNumSerie()   {
        return numSerie;
    }
    public void setNumSerie(long numSerie)   {
        this.numSerie = numSerie;
        refreshData();
    }

    public boolean getFlagAck()    {
        byte[] myFlags = byteTobits(flags);
        if(myFlags[0] == 1)
            return true;
        return false;
    }
    public void setFlagAck(boolean ack) {
        if(flags % 2 != 1)
            flags++;
        refreshData();
    }

    private byte[] byteTobits(byte byteNum)   {
        byte[] array = new byte[8];

        for(int i=0 ; i<8 ; i++)    {
            array[i] = (byte) ( byteNum & 1);
            byteNum>>=1;
        }
        return array;
    }
    private byte[] getNumSerieByte(Long numSerie) {
        byte [] b = new byte[8];
        long l = numSerie;

        for(int i= 1; i <= 8; i++){
            b[8 - i] = (byte)(l >>> (i * 8));
        }
        return b;
    }

    private long getNumSerieLong(byte[] b,int offset)   {
        long l = 0;
        for(int i =0; i < 8; i++){
            l <<= 8;
            l ^= (long)b[i+offset] & 0xFF;
        }

        return l;
    }

    public void resetPacket()   {
        this.numSerie = 0;
        this.tipo = new DatagramType(DatagramType.INIT);
        this.flags = 0;
        byte[] buf1 = {};
        this.setData(buf1);
    }

    @Override
    public DatagramPlusPlusPacket clone()   {
        return new DatagramPlusPlusPacket(this);
    }
}
